package com.dh.blog.handler;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;

import javax.annotation.Resource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.dh.blog.handler.parser.Parser;
import com.dh.blog.handler.xml.Catalog;
import com.dh.blog.handler.xml.XmlCatalog;
import com.dh.blog.handler.xml.XmlNode;
import com.ewei.web.common.exception.MessageRuntimeException;

/**
 * default impl of ArticalHandler
 * @author David
 *
 */
@Component(value="articleHandler")
public class ArticalHandlerImpl implements ArticalHandler {
	private static final Logger logger = LogManager.getLogger(ArticalHandler.class);
	@Resource(name="XPathParser")
	private Parser parser;//to parse the xml
	@Override
	public Catalog<XmlNode> createCatalog(InputStream source,boolean anchor) {
		Document document = resolveToDocument(source);
		if(null != document){
			return createCatalog(document, anchor);
		}
		return null;
	}
	
	@Override
	public Catalog<XmlNode> createCatalog(Document document,boolean anchor) {
		Catalog<XmlNode> catalog = null;
		try {
			NodeList nodeList = parser.evalNodeList("//*[starts-with(name(),'h')]", document);
			catalog = new XmlCatalog();
			if(null != nodeList){
				doCreateCatalog(catalog,nodeList,anchor);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return catalog;
	}

	@Override
	public Document resolveToDocument(InputStream in) {
		Document document = null;
		try{
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			try {
				InputSource source = new InputSource(in);
				source.setEncoding("UTF-8");
				document = builder.parse(source);
			} catch (SAXException e) {
				throw new MessageRuntimeException(e);
			} catch (IOException e) {
				throw new MessageRuntimeException(e);
			}
		}catch(ParserConfigurationException ex){
			throw new MessageRuntimeException(ex);
		}
		return document;
	}

	@Override
	public void addElement(Document doc, Element el) {
		
	}
	/**
	 * create catalog by nodeList
	 * and add anchor attribute if necessary
	 * @param catalog
	 * @param node
	 * @param anchor
	 */
	private void doCreateCatalog(Catalog<XmlNode> catalog,NodeList node,boolean anchor){
		Stack<XmlNode> stack = new Stack<XmlNode>();
		for(int i = 0;i<node.getLength();i++){
			Node child = node.item(i);
			String content = child.getTextContent();
			String tag = child.getNodeName();
			tag = tag.substring(tag.length()-1);
			XmlNode xnode = new XmlNode();
			if(anchor){//add the anchor attribute
				try {
					String value = parser.evalString("//a/@name", child);
					xnode.addAttr("href",value);
				} catch (Exception e) {
					if(e instanceof NullPointerException){
						logger.warn("there are not any anchors existed in article...");
					}else{
						throw new MessageRuntimeException(e);
					}
				}
			}
			xnode.addAttr("h", Integer.valueOf(tag));
			xnode.setText(content);
			if(stack.empty()){
				catalog.addNode(xnode);
			}else{
				addNode(catalog,stack,xnode);
			}
			stack.push(xnode);
		}
	}
	/**
	 * use the stack to add node to catalog recursively
	 * @param catalog
	 * @param node
	 */
	private void addNode(Catalog<XmlNode> catalog,Stack<XmlNode> stack,XmlNode xnode){
		XmlNode tmp = null,result = null;
		while(!stack.empty()){
			tmp = stack.peek();
			if((Integer)tmp.getAttr("h") < 
					(Integer)xnode.getAttr("h")){
				result = tmp;
				break;
			}
			stack.pop();
		}
		if(null == result){
			catalog.addNode(xnode);
		}else{
			catalog.addNode(tmp.getIndex(),xnode);
		}
	}

	@Override
	public void anchor(Document doc, String tag) {
		try {
			NodeList nodeList = parser.evalNodeList("//*[starts-with(name(),'h')]", doc);
			for(int i = 0;i<nodeList.getLength();i++){
				Node node = nodeList.item(i);
				Element el = doc.createElement("a");
				el.setTextContent(node.getTextContent());
				node.setTextContent(null);
				node.appendChild(el);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void saveAndUpdate(Document doc, String path) {
		try{
			TransformerFactory tf = TransformerFactory.newInstance();
			Transformer transformer = tf.newTransformer();
			transformer.setOutputProperty(OutputKeys.ENCODING,"UTF-8");
			DOMSource source = new DOMSource(doc);
			transformer.transform(source, 
					new StreamResult(new File(path)));
		}catch(Exception ex){
			throw new MessageRuntimeException(ex);
		}
	}

	@Override
	public String[] resolveToImgURLs(Document doc) {
		NodeList imgs = null;
		Set<String> urlSet = new HashSet<String>();
		try {
			imgs = parser.evalNodeList("//img", doc);
			for(int i = 0;i<imgs.getLength();i++){
				Node node = imgs.item(i);
				String url = node.getAttributes().getNamedItem("src").getNodeValue();//redundancy?
				urlSet.add(url);
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new MessageRuntimeException(e);
		}
		String[] urls = new String[urlSet.size()];
		return urlSet.toArray(urls);
	}
}
